#include "symbolreader.h"
#include "symbol.h"

#include <QtMath>
#include <QFile>
#include <QXmlStreamWriter>

// TODO: Use logger to print ignored tags
namespace SymbolEditor
{


    Reader::Reader():
        xml(new QXmlStreamReader)
    {
    }

    // TODO: take a QIODevice parameter
    // and get rid of m_errorString
    // return bool here and add Symbol *symbol();
    Symbol *Reader::read(const QString &filename)
    {
        m_symbol = nullptr;
        m_errorString.clear();

        QFile file(filename);
        if (!file.open(QFile::ReadOnly))
        {
            m_errorString = QString("\"%1\": %2").arg(filename).arg(file.errorString());
            return nullptr;
        }

        if (!read(&file))
        {
            return nullptr;
        }

        return symbol();
    }

    bool Reader::read(QIODevice *device)
    {
        m_symbol = nullptr;
        m_errorString.clear();

        xml->setDevice(device);

        if (!xml->readNextStartElement())
        {
            return false;
        }

        if (xml->name() != "symbol" || xml->namespaceUri() != "http://www.leda.org/xdl")
        {
            xml->raiseError("Not a valid XDDL Symbol document");
            return false;
        }

        m_symbol = new Symbol();
        while (xml->readNextStartElement()) {
            if (xml->name() == "name")
            {
                m_symbol->name = xml->readElementText();
            }
            else if (xml->name() == "label")
            {
                m_symbol->description = xml->readElementText();
            }
            else if (xml->name() == "drawing")
            {
                readDrawing();
            }
            else
            {
                xml->skipCurrentElement();
            }
        }

        if (xml->hasError())
        {
            delete m_symbol;
            m_symbol = nullptr;
            return false;
        }

        return true;

    }

    Symbol *Reader::symbol() const
    {
        return m_symbol;
    }

    QString Reader::errorString() const
    {
        if (!m_errorString.isEmpty())
        {
            return m_errorString;
        }
        else
        {
            return QString("%1\nLine %2, column %3")
                    .arg(xml->errorString())
                    .arg(xml->lineNumber())
                    .arg(xml->columnNumber());
        }
    }

    void Reader::readDrawing()
    {
        while (xml->readNextStartElement())
        {
            Item *item;
            if (xml->name() == "circle")
            {
                item = readCircle();
            }
            else if (xml->name() == "circular-arc")
            {
                item = readCircularArc();
            }
            else if (xml->name() == "ellipse")
            {
                item = readEllipse();
            }
            else if (xml->name() == "elliptical-arc")
            {
                item = readEllipticalArc();
            }
            else if (xml->name() == "rectangle")
            {
                item = readRectangle();
            }
            else if (xml->name() == "polyline")
            {
                item = readPolyline();
            }
            else if (xml->name() == "polygon")
            {
                item = readPolygon();
            }
            else
            {
                xml->skipCurrentElement();
                continue;
            }
            m_symbol->drawingItems.append(item);
        }
    }

    void Reader::readItem(Item *item)
    {
        while (xml->readNextStartElement())
        {
            if (xml->name() == "position")
            {
                item->setPosition(readPoint());
            }
            else if (xml->name() == "line-style")
            {
                item->setLineStyle(readLineStyle());
            }
            else if (xml->name() == "line-width")
            {
                item->setLineWidth(readLineWidth());
            }
            else if (xml->name() == "line-color")
            {
                item->setLineColor(readColor(SecondaryContent));
            }
            else if (xml->name() == "fill-style")
            {
                item->setFillStyle(readFillStyle());
            }
            else if (xml->name() == "fill-color")
            {
                item->setFillColor(readColor(BackgroundHighlight));
            }
            else if (xml->name() == "z-value")
            {
                xml->skipCurrentElement();
            }
            else if (xml->name() == "rotation")
            {
                item->setRotation(readAngle());
            }
            else if (xml->name() == "opacity")
            {
                item->setOpacity(readPercentage());
            }
            else if (xml->name() == "locked")
            {
                item->setLocked(readBoolean());
            }
            else if (xml->name() == "x-mirrored")
            {
                item->setXMirrored(readBoolean());
            }
            else if (xml->name() == "y-mirrored")
            {
                item->setYMirrored(readBoolean());
            }
            else if (xml->name() == "visible")
            {
                item->setVisible(readBoolean());
            }
            else
            {
                return;
            }
        }
    }

    Item *Reader::readCircle()
    {
        auto item = new CircleItem;
        readItem(item);

        if (xml->isEndElement())
        {
            return item;
        }

        do
        {
            if (xml->name() == "radius")
            {
                item->setRadius(readLength());
            }
            else
            {
                xml->skipCurrentElement();
            }
        }
        while (xml->readNextStartElement());

        return item;
    }

    Item *Reader::readCircularArc()
    {
        auto item = new CircularArcItem;
        readItem(item);

        if (xml->isEndElement())
        {
            return item;
        }

        do
        {
            if (xml->name() == "radius")
            {
                item->setRadius(readLength());
            }
            else if (xml->name() == "start-angle")
            {
                item->setStartAngle(readAngle());
            }
            else if (xml->name() == "span-angle")
            {
                item->setSpanAngle(readAngle());
            }
            else
            {
                xml->skipCurrentElement();
            }
        }
        while (xml->readNextStartElement());

        return item;
    }

    Item *Reader::readEllipse()
    {
        auto item = new EllipseItem;
        readItem(item);

        if (xml->isEndElement())
        {
            return item;
        }

        do
        {
            if (xml->name() == "x-radius")
            {
                item->setXRadius(readLength());
            }
            else if (xml->name() == "y-radius")
            {
                item->setYRadius(readLength());
            }
            else
            {
                xml->skipCurrentElement();
            }
        }
        while (xml->readNextStartElement());

        return item;
    }

    Item *Reader::readEllipticalArc()
    {
        auto item = new EllipticalArcItem;
        readItem(item);

        if (xml->isEndElement())
        {
            return item;
        }

        do
        {
            if (xml->name() == "x-radius")
            {
                item->setXRadius(readLength());
            }
            else if (xml->name() == "y-radius")
            {
                item->setYRadius(readLength());
            }
            else if (xml->name() == "start-angle")
            {
                item->setStartAngle(readAngle());
            }
            else if (xml->name() == "span-angle")
            {
                item->setSpanAngle(readAngle());
            }
            else
            {
                xml->skipCurrentElement();
            }
        }
        while (xml->readNextStartElement());

        return item;
    }

    Item *Reader::readRectangle()
    {
        auto item = new RectangleItem;
        readItem(item);

        if (xml->isEndElement())
        {
            return item;
        }

        do
        {
            if (xml->name() == "width")
            {
                item->setWidth(readLength());
            }
            else if (xml->name() == "height")
            {
                item->setHeight(readLength());
            }
            else
            {
                xml->skipCurrentElement();
            }
        }
        while (xml->readNextStartElement());

        return item;
    }

    Item *Reader::readPolyline()
    {
        auto item = new PolylineItem;
        readItem(item);

        if (xml->isEndElement())
        {
            return item;
        }

        do
        {
            if (xml->name() == "vertices")
            {
                item->setVertices(readPointList());
            }
            else
            {
                xml->skipCurrentElement();
            }
        }
        while (xml->readNextStartElement());

        return item;
    }

    Item *Reader::readPolygon()
    {
        auto item = new PolygonItem;
        readItem(item);

        if (xml->isEndElement())
        {
            return item;
        }

        do
        {
            if (xml->name() == "vertices")
            {
                item->setVertices(readPointList());
            }
            else
            {
                xml->skipCurrentElement();
            }
        }
        while (xml->readNextStartElement());

        return item;
    }

    Item *Reader::readLabel()
    {
        auto item = new LabelItem;
        readItem(item);

        if (xml->isEndElement())
        {
            return item;
        }

        do
        {
            xml->skipCurrentElement();
        }
        while (xml->readNextStartElement());

        return item;
    }

    Item *Reader::readPin()
    {
        auto item = new PinItem;
        readItem(item);

        if (xml->isEndElement())
        {
            return item;
        }

        do
        {
            xml->skipCurrentElement();
        }
        while (xml->readNextStartElement());

        return item;
    }

    Item *Reader::readGroup()
    {
        auto item = new ItemGroup;
        readItem(item);

        if (xml->isEndElement())
        {
            return item;
        }

        do
        {
            xml->skipCurrentElement();
        }
        while (xml->readNextStartElement());

        return item;
    }

    QPointF Reader::readPoint()
    {
        QPointF point;
        while (xml->readNextStartElement())
        {
            if (xml->name() == "x")
            {
                point.setX(readReal());
            }
            else if (xml->name() == "y")
            {
                point.setY(readReal());
            }
            else
            {
                xml->skipCurrentElement();
            }
        }
        return point;
    }

    QList<QPointF> Reader::readPointList()
    {
        QList<QPointF> points;
        while (xml->readNextStartElement())
        {
            if (xml->name() == "point")
            {
                points.append(readPoint());
            }
            else
            {
                xml->skipCurrentElement();
            }
        }
        return points;
    }

    LineStyle Reader::readLineStyle()
    {
        auto value = xml->readElementText();
        if (value =="None")
        {
            return NoLine;
        }
        if (value =="Solid")
        {
            return SolidLine;
        }
        if (value =="Dash")
        {
            return DashLine;
        }
        if (value =="Dot")
        {
            return DotLine;
        }
        if (value =="DashDot")
        {
            return DashDotLine;
        }
        if (value =="DashDotDot")
        {
            return DashDotDotLine;
        }
        return SolidLine;
    }

    LineWidth Reader::readLineWidth()
    {
        auto value = xml->readElementText();
        if (value =="Thinest")
        {
            return ThinestLine;
        }
        if (value =="Thiner")
        {
            return ThinerLine;
        }
        if (value =="Thin")
        {
            return ThinLine;
        }
        if (value =="SlightlyThin")
        {
            return SlightlyThinLine;
        }
        if (value =="Medium")
        {
            return MediumLine;
        }
        if (value =="SlightlyThick")
        {
            return SlightlyThickLine;
        }
        if (value =="Thick")
        {
            return ThickLine;
        }
        if (value =="Thicker")
        {
            return ThickerLine;
        }
        if (value =="Thickest")
        {
            return ThickestLine;
        }
        return MediumLine;
    }

    Color Reader::readColor(Color defaultColor)
    {
        auto value = xml->readElementText();
        if (value =="PrimaryContent")
        {
            return PrimaryContent;
        }
        if (value =="SecondaryContent")
        {
            return SecondaryContent;
        }
        if (value =="EmphasisedContent")
        {
            return EmphasisedContent;
        }
        if (value =="Background")
        {
            return Background;
        }
        if (value =="BackgroundHighlight")
        {
            return BackgroundHighlight;
        }
        if (value =="Yellow")
        {
            return Yellow;
        }
        if (value =="Orange")
        {
            return Orange;
        }
        if (value =="Red")
        {
            return Red;
        }
        if (value =="Magenta")
        {
            return Magenta;
        }
        if (value =="Violet")
        {
            return Violet;
        }
        if (value =="Blue")
        {
            return Blue;
        }
        if (value =="Cyan")
        {
            return Cyan;
        }
        if (value =="Green")
        {
            return Green;
        }
        return defaultColor;
    }

    FillStyle Reader::readFillStyle()
    {
        auto value = xml->readElementText();
        if (value =="None")
        {
            return NoFill;
        }
        if (value =="Solid")
        {
            return SolidFill;
        }
        return SolidFill;
    }

    qreal Reader::readReal()
    {
        return xml->readElementText().toDouble();
    }

    qreal Reader::readPercentage()
    {
        qreal value = readReal();
        if (value > 100.0)
        {
            return 100.0;
        }
        if (value < 0.0)
        {
            return 0.0;
        }
        return value;
    }

    qreal Reader::readAngle()
    {
        qreal value = readReal();
        if (value > 360.0)
        {
            return 0.0;
        }
        if (value < 0.0)
        {
            return 0.0;
        }
        return value;
    }

    qreal Reader::readLength()
    {
        qreal value = readReal();
        if (value < 0.0)
        {
            return 0.0;
        }
        return value;
    }

    bool Reader::readBoolean()
    {
        auto value = xml->readElementText();
        if (value.compare("true", Qt::CaseInsensitive) == 0)
        {
            return true;
        }
        if (value.compare("false", Qt::CaseInsensitive) == 0)
        {
            return false;
        }
        return false;
    }

}
